﻿using AutoMapper;
using AutoMapper.Internal;
using Cyss.Core.AutoMapper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Cyss.Core
{


    public static class MappingExtensions
    {

        private static MapperConfiguration _MapperConfiguration;
        private static IMapper _Mapper;
        private static List<MapperType> MapperTypes = new List<MapperType>();
        private static object objLock = new object();
        private static IDefaultCreateOrUpdateName _defaultCreateOrUpdateName { set; get; }
        #region Utilities

        static MappingExtensions()
        {
            _defaultCreateOrUpdateName = IOCEngine.Resolve<IDefaultCreateOrUpdateName>();
        }

        /// <summary>
        /// Execute a mapping from the source object to a new destination object. The source type is inferred from the source object
        /// </summary>
        /// <typeparam name="TDestination">Destination object type</typeparam>
        /// <param name="source">Source object to map from</param>
        /// <returns>Mapped destination object</returns>
        private static TDestination Map<TDestination>(this object source)
        {
            var sourceType = source.GetType();
            var destinationType = typeof(TDestination);
            return GetMapper(sourceType, destinationType).Map<TDestination>(source);
        }

        /// <summary>
        /// Execute a mapping from the source object to the existing destination object
        /// </summary>
        /// <typeparam name="TSource">Source object type</typeparam>
        /// <typeparam name="TDestination">Destination object type</typeparam>
        /// <param name="source">Source object to map from</param>
        /// <param name="destination">Destination object to map into</param>
        /// <returns>Mapped destination object, same instance as the passed destination object</returns>
        private static TDestination MapTo<TSource, TDestination>(this TSource source, TDestination destination)
        {
            var sourceType = source.GetType();
            var destinationType = destination.GetType();
            return GetMapper(sourceType, destinationType).Map(source, destination);
        }

        private static IMapper GetMapper(Type sourceType, Type destinationType)
        {
            var map = AutoMapper.AutoMapperConfiguration.MapperConfiguration.Internal().GetAllTypeMaps().FirstOrDefault(x => x.Types.SourceType == sourceType && x.Types.DestinationType == destinationType);
            if (map != null)
            {
                return AutoMapperConfiguration.Mapper;
            }
            if (MappingExtensions.MapperTypes.Any(x => x.sourceType == sourceType && x.destinationType == destinationType))
            {
                return _Mapper;
            }
            lock (objLock)
            {
                if (MappingExtensions.MapperTypes.Any(x => x.sourceType == sourceType && x.destinationType == destinationType))
                {
                    return _Mapper;
                }

                MappingExtensions.MapperTypes.Add(new MapperType() { sourceType = sourceType, destinationType = destinationType });
                _MapperConfiguration = new MapperConfiguration(ctx =>
                {
                    foreach (var item in MappingExtensions.MapperTypes)
                    {
                        ctx.CreateMap(item.sourceType, item.destinationType);
                    }
                }
                );

                _Mapper = _MapperConfiguration.CreateMapper();
            }
            return _Mapper;
        }
        #endregion

        #region Methods

        #region Model-Entity mapping


        /// <summary>
        /// Execute a mapping from the entity to a new model
        /// </summary>
        /// <typeparam name="TModel">Model type</typeparam>
        /// <param name="entity">Entity to map from</param>
        /// <returns>Mapped model</returns>
        public static TModel ToObject<TModel>(this object entity) where TModel : class, new()
        {
            if (entity == null)
                return null;
            return entity.Map<TModel>();
        }


        /// <summary>
        /// Execute a mapping from the entity to a new model
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <param name="entity"></param>
        /// <param name="IsDefaultCreateOrUpdateName">时候自动装载创建人名称和更新人名称</param>
        /// <returns></returns>
        public static TModel ToModel<TModel>(this BaseEntity entity) where TModel : BaseModel
        {
            if (entity == null)
                return null;
            return entity.Map<TModel>();
        }

        /// <summary>
        /// Execute a mapping from the entity to a new model
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <param name="entity"></param>
        /// <param name="IsDefaultCreateOrUpdateName">时候自动装载创建人名称和更新人名称</param>
        /// <returns></returns>
        public async static Task<TModel> ToModel<TModel>(this BaseEntity entity, bool IsDefaultCreateOrUpdateName = false) where TModel : BaseDefaultEntityModel
        {
            if (entity == null)
                return null;

            var model = entity.Map<TModel>();
            if (IsDefaultCreateOrUpdateName)
            {
                await _defaultCreateOrUpdateName?.Default(model);
            }
            return model;
        }




        /// <summary>
        /// Execute a mapping from the entity to a new model
        /// </summary>
        /// <typeparam name="TModel">Model type</typeparam>
        /// <param name="entity">Entity to map from</param>
        /// <returns>Mapped model</returns>
        public static TModel ToModel<TModel>(this BaseViewModel entity) where TModel : BaseModel
        {
            if (entity == null)
                return null;


            return entity.Map<TModel>();
        }



        /// <summary>
        /// Execute a mapping from the entity to a new model
        /// </summary>
        /// <typeparam name="TModel">Model type</typeparam>
        /// <param name="entity">Entity to map from</param>
        /// <returns>Mapped model</returns>
        public static TVModel ToVModel<TVModel>(this BaseModel entity) where TVModel : BaseViewModel
        {
            if (entity == null)
                return null;


            return entity.Map<TVModel>();
        }



        /// <summary>
        /// Execute a mapping from the model to a new entity
        /// </summary>
        /// <typeparam name="TEntity">Entity type</typeparam>
        /// <param name="model">Model to map from</param>
        /// <returns>Mapped entity</returns>
        public static TEntity ToEntity<TEntity>(this BaseModel model) where TEntity : BaseEntity
        {
            if (model == null)
                return null;

            return model.Map<TEntity>();
        }




        /// <summary>
        /// Execute a mapping from the entity to the existing model
        /// </summary>
        /// <typeparam name="TEntity">Entity type</typeparam>
        /// <typeparam name="TModel">Model type</typeparam>
        /// <param name="entity">Entity to map from</param>
        /// <param name="model">Model to map into</param>
        /// <returns>Mapped model</returns>
        public static TModel ToModel<TEntity, TModel>(this TEntity entity, TModel model)
            where TEntity : BaseEntity where TModel : BaseModel
        {
            if (entity == null)
                return null;

            if (model == null)
                return null;

            return entity.MapTo(model);
        }



        /// <summary>
        /// Execute a mapping from the model to the existing entity
        /// </summary>
        /// <typeparam name="TEntity">Entity type</typeparam>
        /// <typeparam name="TModel">Model type</typeparam>
        /// <param name="model">Model to map from</param>
        /// <param name="entity">Entity to map into</param>
        /// <returns>Mapped entity</returns>
        public static TEntity ToEntity<TEntity, TModel>(this TModel model, TEntity entity)
            where TEntity : BaseEntity where TModel : BaseModel
        {
            if (model == null)
                return null;

            if (entity == null)
                return null;

            return model.MapTo(entity);
        }

        #endregion

        #endregion
    }

}
